package cn.cuilan.client.core;

import cn.cuilan.common.client.ProtocolMessageClient;
import cn.cuilan.common.entity.ProtocolMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.function.Supplier;

/**
 * 私有协议客户端对象池
 *
 * @param <T> 私有协议消息对象
 * @author zhang.yan
 */
@Slf4j
@Component
public class ClientPool<T extends ProtocolMessage> {

    private GenericObjectPool<ProtocolMessageClient<T>> pool;

    /**
     * 获取一个客户端
     *
     * @param createClient 初始化参数
     * @return 返回私有协议客户端
     */
    public ProtocolMessageClient<T> getClient(Supplier<ProtocolMessageClient<T>> createClient) throws Exception {
        if (pool == null) {
            synchronized (this) {
                if (pool == null) {
                    pool = createPool(createClient);
                }
            }
        }
        return pool.borrowObject();
    }

    /**
     * 创建对象池
     */
    public GenericObjectPool<ProtocolMessageClient<T>> createPool(Supplier<ProtocolMessageClient<T>> createClient) {
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        // 最大空闲数
        poolConfig.setMaxIdle(5);
        // 最小空闲数, 池中只有一个空闲对象的时候，池会在创建一个对象，并借出一个对象，从而保证池中最小空闲数为1
        poolConfig.setMinIdle(0);
        // 最大池对象总数
        poolConfig.setMaxTotal(20);
        // 逐出连接的最小空闲时间 默认1800000毫秒
        poolConfig.setMinEvictableIdleTimeMillis(1000 * 10);
        // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
        poolConfig.setTimeBetweenEvictionRunsMillis(1000 * 10);
        // 在获取对象的时候检查有效性, 默认false
        poolConfig.setTestOnBorrow(true);
        // 在空闲时检查有效性, 默认false
        poolConfig.setTestWhileIdle(false);
        // 最大等待时间， 默认的值为-1，表示无限等待。
        poolConfig.setMaxWaitMillis(5000);
        // 是否启用后进先出, 默认true
        poolConfig.setLifo(true);
        // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
        poolConfig.setBlockWhenExhausted(true);
        // 每次逐出检查时 逐出的最大数目 默认3
        poolConfig.setNumTestsPerEvictionRun(3);

        // 创建对象池
        return new GenericObjectPool<>(new BasePooledObjectFactory<ProtocolMessageClient<T>>() {

            @Override
            public ProtocolMessageClient<T> create() throws Exception {
                return createClient.get();
            }

            @Override
            public PooledObject<ProtocolMessageClient<T>> wrap(ProtocolMessageClient<T> client) {
                return new DefaultPooledObject<>(client);
            }

            @Override
            public void destroyObject(PooledObject<ProtocolMessageClient<T>> p) throws Exception {
                if (p.getObject().isConnected()) {
                    p.getObject().disconnect();
                }
                p.getObject().shutdown();
            }

            @Override
            public boolean validateObject(PooledObject<ProtocolMessageClient<T>> p) {
                return p.getObject().isConnected();
            }
        }, poolConfig);
    }

    /**
     * 释放对象
     */
    public void returnClient(ProtocolMessageClient<T> client) {
        pool.returnObject(client);
    }

    /**
     * 每隔10分钟执行一次
     */
    @Scheduled(fixedDelay = 60 * 1000 * 10)
    public void showLog() {
        if (pool != null) {
            log.info("线程池使用情况, [使用数: {}, 空闲数: {}]", pool.getNumActive(), pool.getNumIdle());
        }
    }

}
